package com.qire.antsbinder.dal;

import android.os.Handler;
import android.os.Looper;

import com.qire.antsbinder.dal.annotation.StrategyAnnotation;
import com.qire.antsbinder.dal.annotation.StrategyAnnotation.ThreadMode;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * <pre>
 *     数据服务基类，用于数据交互访问和通知层。
 *     实现了DAL层的Observer接口，简单的实现了对数据访问时回调Service事件的路由机制。
 *     DAL层访问了网络数据或者本地数据成功后会通知到Service的相关关注行为方法，
 *     通过StrategyAnnotation注解标注改方法关注的数据接口。
 * </pre>
 */
public abstract class DataService implements Observer {

    HashMap<String, Method>     strategyMap     = new HashMap<>();
    HashMap<String, ThreadBus>  threadBusMap    = new HashMap<>();

    public DataService() {
        loadingStrategyRoute(getClass());
    }

    //FIXME:O(n*n),该方法存在一定风险，使用了递归算法，继承过多可能导致性能问题。尽量控制在继承4次以内。
    /**
     * 加载策略路由
     * @param cls 策略服务提供者
     */
    private final void loadingStrategyRoute(Class cls) {
        Class superClass = cls.getSuperclass();
        if(superClass == null) {
            return;
        }
        if(!superClass.equals(DataService.class)) {
            loadingStrategyRoute(superClass);
        }
        Method[] methods = cls.getDeclaredMethods();
        for(Method method : methods) {
            if(method.isAnnotationPresent(StrategyAnnotation.class)) {
                StrategyAnnotation annotation = method.getAnnotation(StrategyAnnotation.class);
                String[]    events      = annotation.event();
                ThreadMode  threadMode  = annotation.threadMode();
                String      threadAlias = annotation.threadAlias();
                for(String event : events) {
                    strategyMap.put(event, method);
                    threadBusMap.put(event, new ThreadBus(threadMode, threadAlias));
                }
            }
        }
    }

    @Override
    public <T> void update(String msg, T data, boolean isCache) {
        Method      strategyMethod  = strategyMap.get(msg);
        ThreadBus   threadBus       = threadBusMap.get(msg);
        if(strategyMethod != null) {
            StrategyMethodProcess process = new StrategyMethodProcess(strategyMethod, this, data, isCache);
            if(threadBus == null) {
                process.doProcess();
            } else {
                threadBus.dispatchThread(process);
            }
        }
    }

    /**
     * 线程巴士:负责根据threadMode调度执行线程*/
    final class ThreadBus {

        private final ThreadMode threadMode;
        private final String     threadAlias;

        private ThreadBus(ThreadMode threadMode, String threadAlias) {
            this.threadMode  = threadMode;
            this.threadAlias = threadAlias;
        }

        private final void dispatchThread(StrategyMethodProcess process) {
            switch (threadMode) {
                case MAIN :
                    if(Looper.myLooper() == Looper.getMainLooper()) {
                        process.doProcess();
                    } else {
                        Handler mainHandler = new Handler(Looper.getMainLooper());
                        mainHandler.post(process);
                    }
                    return;
                case POSTING :
                    process.doProcess();
                    return;
                case ASYNC :
                    new Thread(process, threadAlias).start();
                    return;
            }
        }

    }

    /**
     * 策略方法处理过程：封装在runnable接口中使线程调度使用起来更方便*/
    final class StrategyMethodProcess<T> implements Runnable {

        private final Method strategyMethod;
        private final Object obj;
        private final T data;
        private final boolean isCache;

        private StrategyMethodProcess(Method strategyMethod, Object obj, T data, boolean isCache) {
            this.strategyMethod = strategyMethod;
            this.obj            = obj;
            this.data           = data;
            this.isCache        = isCache;
        }

        @Override
        public void run() {
            doProcess();
        }

        private void doProcess() {
            try {
                strategyMethod.invoke(obj, data, isCache);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

}
